/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package bgu.dcr.tp.cfg;

import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 *
 * @author Shl
 */
public class Configuration {

    private static ConcurrentHashMap<Class, MetaConfiguration> cachedConfigurations = new ConcurrentHashMap<>();

    MetaConfiguration conf;
    IdentityHashMap<Object, Object> values = new IdentityHashMap<>();

    private Configuration(MetaConfiguration conf) {
        this.conf = conf;
    }

    public static Configuration scan(Class c) {
        MetaConfiguration result = cachedConfigurations.get(c);
        if (result == null) {
            result = new MetaConfiguration(c);
            MetaConfiguration temp = cachedConfigurations.putIfAbsent(c, result);
            if (temp != null) {
                result = temp;
            }
        }

        return new Configuration(result);
    }

    public String getPropertyValue(ConfigurableElement property) {
        if (property.isSocket()) {
            throw new ConfigurationException("an attempt to treat socket as property has made: " + property);
        }
        String value = (String) values.get(property);
        if (value == null) {
            value = property.getDefaultValue();
        }
        return value;
    }

    public Configuration getSocketValue(ConfigurableElement sock) {
        if (!sock.isSocket()) {
            throw new ConfigurationException("an attempt to treat property as socket has made: " + sock);
        }

        return (Configuration) values.get(sock);
    }

    public void setPropertyValue(ConfigurableElement property, String value) {
        values.put(property, value);
    }

    public void setSocketValue(ConfigurableElement sock, Configuration value) {
        values.put(sock, value);
    }

    public void load(InputStream in) {

    }

    public void load(Object o) {

    }

    public void save(OutputStream out) {

    }

    public Object createObject() {
        throw new UnsupportedOperationException("not implemented yet");
    }

    private static class MetaConfiguration {

        private Map<String, ConfigurableElement> elements = new HashMap<>();
        private Class type;

        public MetaConfiguration(Class c) {
            this.type = c;

            for (Method m : c.getMethods()) {
                m.setAccessible(true);
                MarkSocket ms = m.getAnnotation(MarkSocket.class);
                MarkProperty mp = m.getAnnotation(MarkProperty.class);

                if (ms != null && mp != null) {
                    throw new ConfigurationException("Method: " + m.getName() + " of class " + c.getCanonicalName() + " marked both as Socket and Property");
                }

                if (ms != null) {
                    readSocket(c, m, ms);
                } else if (mp != null) {
                    readProperty(c, m, mp);
                }
            }
        }

        private void readSocket(Class c, Method m, MarkSocket ms) {
            ConfigurableElement e = new ConfigurableElement(c, m, ms);
            if (elements.put(e.getName(), e) != null) {
                throw new ConfigurationException("class " + c.getCanonicalName() + " has two different elements marked with the same name: " + e.getName());
            }
        }

        private void readProperty(Class c, Method m, MarkProperty mp) {
            ConfigurableElement e = new ConfigurableElement(c, m, mp);
            if (elements.put(e.getName(), e) != null) {
                throw new ConfigurationException("class " + c.getCanonicalName() + " has two different elements marked with the same name: " + e.getName());
            }
        }

    }

}
